package com.atguigu.gulimall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.to.es.SkuEsModel;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.search.config.GulimallElasticSearchConfig;
import com.atguigu.gulimall.search.constant.EsConstant;
import com.atguigu.gulimall.search.feign.ProductFeignService;
import com.atguigu.gulimall.search.service.MallSearchService;
import com.atguigu.gulimall.search.vo.AttrResponseVo;
import com.atguigu.gulimall.search.vo.BrandVo;
import com.atguigu.gulimall.search.vo.SearchParam;
import com.atguigu.gulimall.search.vo.SearchResult;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.RangeQueryBuilder;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * User: haitao
 * Date: 2020/7/5
 */
@Service
public class MallSearchServiceImpl implements MallSearchService {
    @Qualifier("restHighLevelClient")
    @Autowired
    private RestHighLevelClient client;
    @Autowired
    private ProductFeignService productFeignService;

    /**
     * 去es 进行检索
     *
     * @param param 检索的所有参数
     * @return
     */
    @Override
    public SearchResult search(SearchParam param) {
        // 1. 动态构建出，查询的DSL 语句
        SearchResult result = null;
        // 1. 准备检索请求
        SearchRequest searchRequest = buildSearchRequest(param);
        try {
            // 2. 执行检索请求
            SearchResponse response = client.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
            // 3. 分析响应数据，封装成我们需要的格式
            result = buildSearchResult(response, param);

        } catch (Exception e) {
            // e.printStackTrace();
        } finally {

        }
        return result;
    }

    /**
     * 构建结果数据
     *
     * @param response
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
        SearchResult result = new SearchResult();
        SearchHits hits = response.getHits();
        //1、 返回的所有查询到的商品
        List<SkuEsModel> products = new ArrayList<>();
        if (hits.getHits() != null && hits.getHits().length > 0) {
            products = Stream.of(hits.getHits()).map(item -> {
                SkuEsModel skuEsModel = JSON.parseObject(item.getSourceAsString(), SkuEsModel.class);
                if (!StringUtils.isEmpty(param.getKeyword())) {
                    HighlightField highlightField = item.getHighlightFields().get("skuTitle");
                    String skuTitle = highlightField.getFragments()[0].string();
                    skuEsModel.setSkuTitle(skuTitle);
                }
                return skuEsModel;

            }).collect(Collectors.toList());
        }
        result.setProducts(products);

        // 2、当前所有商品设计到的所有属性信息
        ParsedNested attr = response.getAggregations().get("attr");
        ParsedLongTerms attr_id_agg = attr.getAggregations().get("attr_id_agg");
        List<SearchResult.AttrVo> attrs = attr_id_agg.getBuckets().stream()
                .map(bucket -> {
                    SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
                    // 属性的id
                    String id = bucket.getKeyAsString();
                    attrVo.setAttrId(Long.parseLong(id));
                    // 属性的名字
                    ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
                    String name = attr_name_agg.getBuckets().get(0).getKeyAsString();
                    attrVo.setAttrName(name);

                    // 设置属性值
                    ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
                    List<String> values = attr_value_agg.getBuckets().stream()
                            .map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
                    attrVo.setAttrValue(values);


                    return attrVo;
                }).collect(Collectors.toList());
        result.setAttrs(attrs);
        // 3、当前所有商品涉及到的所有品牌信息
        ParsedLongTerms brand_agg = response.getAggregations().get("brand_agg");
        List<SearchResult.BrandVo> brands = brand_agg.getBuckets().stream()
                .map(bucket -> {
                    SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
                    // 设置品牌id
                    String brandId = bucket.getKeyAsString();
                    brandVo.setBrandId(Long.parseLong(brandId));
                    // 设置品牌名字
                    ParsedStringTerms brand_name_agg = bucket.getAggregations().get("brand_name_agg");
                    String name = brand_name_agg.getBuckets().get(0).getKeyAsString();
                    brandVo.setBrandName(name);
                    // 设在品牌图片
                    ParsedStringTerms brand_img_agg = bucket.getAggregations().get("brand_img_agg");
                    String img = brand_img_agg.getBuckets().get(0).getKeyAsString();
                    brandVo.setBrandImg(img);

                    return brandVo;
                }).collect(Collectors.toList());
        result.setBrands(brands);
        // 4、当前所有商品涉及到的所有分类数据

        ParsedLongTerms catelog_agg = response.getAggregations().get("catelog_agg");
        List<SearchResult.CatalogVo> catalogs = catelog_agg.getBuckets().stream()
                .map(bucket -> {
                    // 设置分类id
                    SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
                    String catelog = bucket.getKeyAsString();
                    catalogVo.setCatalogId(Long.valueOf(catelog));
                    // 设置分类名字
                    ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
                    String catalogName = catalog_name_agg.getBuckets().get(0).getKeyAsString();
                    catalogVo.setCatalogName(catalogName);
                    return catalogVo;

                }).collect(Collectors.toList());
        result.setCatalogs(catalogs);


        // ================以上是从聚合分析里面获取=====================
        // 5、 分页信息-页码  (page - 1 ) * size == from
        result.setPageNum(param.getPageNum());
        // 6、分页信息-总记录数
        long total = hits.getTotalHits().value;
        result.setTotal(total);
        // 7、分页信息-总页码
        int totalPages = (int) total % EsConstant.PRODUCT_PAGESIZE == 0 ? (int) total / EsConstant.PRODUCT_PAGESIZE : ((int) total / EsConstant.PRODUCT_PAGESIZE + 1);
        result.setTotalPages(totalPages);

        // 8、导航页，为了方便遍历有几页
        List<Integer> pageNavs = new ArrayList<>();

        for (int i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        result.setPageNavs(pageNavs);

        // 9.构建面包屑导航功能
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            List<SearchResult.NavVo> navVos = param.getAttrs().stream()
                    .map(a -> {
                        // 分析每个attrs 传过来的查询参数值
                        SearchResult.NavVo navVo = new SearchResult.NavVo();
                        // attrs=2_5寸:6寸
                        String[] s = a.split("_");
                        navVo.setNavValue(s[1]);
                        R r = productFeignService.attrInfo(Long.valueOf(s[0]));
                        // 这个属性id 已经被用了
                        result.getAttrIds().add(Long.valueOf(Long.valueOf(s[0])));
                        if (r.getCode() == 0) {
                            AttrResponseVo attrResponseVo = r.getData("attr", new TypeReference<AttrResponseVo>() {
                            });
                            navVo.setNavName(attrResponseVo.getAttrName());
                        } else {
                            navVo.setNavName(s[0]);
                        }

                        // 取消了面包屑以后，跳转到那个地方。将请求地址的对应属性值去掉即可。
                        // 拿到所有的查询条件，去掉当前
                        String replace = replaceQueryString(param, a, "attrs");
                        navVo.setLink("http://search.gulimall.com/list.html?" + replace);
                        return navVo;
                    }).collect(Collectors.toList());
            result.setNavs(navVos);
        }
        // 品牌、分类的面包屑
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            List<SearchResult.NavVo> navs = result.getNavs();
            SearchResult.NavVo navVo = new SearchResult.NavVo();

            navVo.setNavName("品牌");
            // TODO 远程查询所有品牌
            R r = productFeignService.brandsInfo(param.getBrandId());
            if (r.getCode() == 0) {
                List<BrandVo> brandsVo = r.getData("brand", new TypeReference<List<BrandVo>>() {
                });
                StringBuffer stringBuffer = new StringBuffer();
                String replace = "";
                for (BrandVo brandVo : brandsVo) {
                    stringBuffer.append(brandVo.getName() + ";");
                    replace = replaceQueryString(param, brandVo.getBrandId() + "", "brandId");
                }
                navVo.setNavValue(stringBuffer.toString()); // 面包屑的值
                navVo.setLink("http://search.gulimall.com/list.html?" + replace); // 面包屑对应的url地址
            }
            navs.add(navVo);
        }
        // TODO 分类，分类不需要导航取消

        return result;
    }

    private String replaceQueryString(SearchParam param, String value, String key) {
        String encode = "";
        try {
            // TODO  注意浏览器编码 空格 为%20 java编码成 +
            encode = URLEncoder.encode(value, "UTF-8");
            encode = encode.replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        // 这里需要注意 可能是 ?brandId=1 或者是 &brandId=1
        // HttpServletRequest.queryString http://http://search.gulimall.com/list.html?brandId=9 取得的值是 brandId=9 把问号去掉了
        // HttpServletRequest.queryString http://http://search.gulimall.com/list.html?1=1&brandId=9 取得的值是 1=1&brandId=9 把问号去掉了

        return param.get_queryString()
                .replace("&" + key + "=" + encode, "")
                .replace(key + "=" + encode, "");
    }

    /**
     * 准备检索请求
     * 模糊匹配（评分），过滤（按照属性，分类，品牌，价格区间，库存），排序，分页，高亮，聚合分析
     *
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        // 构建DSL语句
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        // 1、构建 bool - query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();

        /**
         * 查询：模糊匹配（评分），过滤（按照属性，分类，品牌，价格区间，库存）
         */
        // 1.1、must - query
        if (!StringUtils.isEmpty(param.getKeyword())) {
            boolQueryBuilder.must().add(QueryBuilders.matchQuery("skuTitle", param.getKeyword()));
        }

        // ① 构建分类过滤 catalogId - filter
        if (param.getCatalog3Id() != null && !param.getCatalog3Id().equals(0L)) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", param.getCatalog3Id()));
        }
        // ② 构建品牌过滤 brandId - filter
        if (param.getBrandId() != null && param.getBrandId().size() > 0) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", param.getBrandId()));
        }
        // ③ 构建属性过滤 attrs - filter
        if (param.getAttrs() != null && param.getAttrs().size() > 0) {
            // attrs=1_5寸:8寸&attrs=2_8G:16G
            for (String attr : param.getAttrs()) {
                // 多个属性查询需要多个nested，所以放在里面
                BoolQueryBuilder boolQueryBuilder1 = QueryBuilders.boolQuery();
                String[] s = attr.split("_");
                Long attrId = Long.valueOf(s[0]);
                String[] attrValue = s[1].split(":");

                boolQueryBuilder1.must(QueryBuilders.termQuery("attrs.attrId", attrId));
                boolQueryBuilder1.must(QueryBuilders.termsQuery("attrs.attrValue", attrValue));
                boolQueryBuilder.filter(QueryBuilders.nestedQuery("attrs", boolQueryBuilder1, ScoreMode.None));
            }

        }

        // ④ 构建库存过滤 hasStock - filter
        if (param.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", param.getHasStock() == 1));
        }
        // ⑤ 构建价格过滤 skuPrice - filter
        if (!StringUtils.isEmpty(param.getSkuPrice())) {
            // skuPrice -> 1_500/_500/500_
            RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
            String[] str = param.getSkuPrice().split("_");
            if (str.length == 2) { // skuPrice -> 1_500
                rangeQuery.gte(str[0]).lte(str[1]);
            } else if (str.length == 1) {
                if (param.getSkuPrice().startsWith("_")) { // skuPrice -> _500
                    rangeQuery.lte(str[0]);
                }
                if (param.getSkuPrice().endsWith("_")) { // skuPrice -> 500_
                    rangeQuery.gte(str[0]);
                }
            }
            boolQueryBuilder.filter(rangeQuery);

        }


        // 将封装好的bool 语句封装给searchSourceBuilder
        searchSourceBuilder.query(boolQueryBuilder);

        /**
         * 排序，分页，高亮
         */
        if (!StringUtils.isEmpty(param.getSort())) {
            // 排序 saleCount_asc/desc  skuPrice_asc/desc hotScore_asc/desc
            String[] split = param.getSort().split("_");
            searchSourceBuilder.sort(split[0], "asc".equalsIgnoreCase(split[1]) ? SortOrder.ASC : SortOrder.DESC);
        }


        // 高亮
        if (!StringUtils.isEmpty(param.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            searchSourceBuilder.highlighter(highlightBuilder);
        }

        // 分页
        if (param.getPageNum() != null) {
            searchSourceBuilder.from((param.getPageNum() - 1) * EsConstant.PRODUCT_PAGESIZE);
            searchSourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        }


        /**
         * 聚合分析
         */
        // 聚合分析：品牌的id、品牌的名字
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg
                .field("brandId")
                .size(100)
                .subAggregation(AggregationBuilders.terms("brand_name_agg")
                        .field("brandName")
                        .size(1))
                .subAggregation(AggregationBuilders.terms("brand_img_agg")
                        .field("brandImg")
                        .size(1));

        // 聚合分析：分类的id、分类的名字
        TermsAggregationBuilder catelog_agg = AggregationBuilders.terms("catelog_agg");
        catelog_agg
                .field("catalogId")
                .size(100)
                .subAggregation(AggregationBuilders.terms("catalog_name_agg")
                        .field("catalogName")
                        .size(1));

        // 聚合分析：属性id、每个属性的名字和属性值
        NestedAggregationBuilder nested_attrs_agg = AggregationBuilders.nested("attr", "attrs")
                .subAggregation(
                        AggregationBuilders.terms("attr_id_agg")
                                .field("attrs.attrId")
                                .size(100)
                                .subAggregation(
                                        AggregationBuilders.terms("attr_name_agg")
                                                .field("attrs.attrName")
                                                .size(1))
                                .subAggregation(
                                        AggregationBuilders.terms("attr_value_agg")
                                                .field("attrs.attrValue")
                                                .size(100)));

        // 将写好的聚合语句设置到searchSourceBuilder 中
        searchSourceBuilder.aggregation(brand_agg).aggregation(catelog_agg).aggregation(nested_attrs_agg);


        // 构建最懂的SearchRequest
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.indices(new String[]{EsConstant.PRODUCT_INDEX}).source(searchSourceBuilder);
        return searchRequest;
    }

}
